﻿using System;
using System.Linq;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Windows.Forms;
using Commands.Settings;
using Commands.TorrentDownloaders;
using System.Collections.Generic;
using Uploaders;
using Commands.Commands;

namespace Commands
{
    /// <summary>
    /// Class with commands which can be executed.
    /// </summary>
    internal class CommandsList : CommandListBase
    {
        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public CommandsList(IMailer mailer, ITorrentManager manager)
        {
            _torrentManager = manager;
            _mailer = mailer;
        }

        #endregion

        #region Commands

        /// <summary>
        /// Save attached file.
        /// </summary>
        [RemoteCommand("Save attached file", "save", "saveattachment")]
        public bool SaveAttachment(
            [RequiredParameter("Attachment name(including extension).", typeof(string), "n", "name", "fileName")] string fileName,
            [FolderWithSavedAttachesParameter] string folderWithSavedAttaches, 
            [RequiredParameter("Path where file must be placed(including extension).", 
                typeof(string), "s", "save", "savePath")] string savePath)
        {
            File.Move(Path.Combine(folderWithSavedAttaches, fileName), savePath);

            return true;
        }

        /// <summary>
        /// Start downloading torrent.
        /// </summary>
        /// <param name="torrentFile">Torrent file name</param>
        /// <param name="folderWithSavedAttaches"></param>
        /// <param name="saveDirectory">Directory to save torrent content.</param>
        [RemoteCommand("Start downloading torrent.", "torrent", "add")]
        public bool AddTorrent(
            [RequiredParameter("Torrent file name.", typeof(string), "n", "torrent", "file", "torrentFile")] string torrentFile,
            [FolderWithSavedAttachesParameter] string folderWithSavedAttaches,
            [DirectoryParameterAttribute("Directory to save torrent content.", false)] string saveDirectory)
        {
            // Get .torrent file path.
            string filePath = Path.Combine(folderWithSavedAttaches, torrentFile);

            return TorrentManager.StartTorrent(filePath, saveDirectory );
        }

        /// <summary>
        /// Start downloading torrent.
        /// </summary>
        /// <param name="url">Torrent file URL.</param>
        /// <param name="saveDirectory">Directory to save torrent torrent. </param>
        [RemoteCommand("Download torrent from URL and start it.", "tdownload", "td")]
        public bool DownloadTorrent([URLRequiredParameter("Torrent file URL.")] string url, 
            [DirectoryParameterAttribute("Directory to save torrent content.", false)] string saveDirectory,
            [Parameter("Mapping folder name.", typeof(string), false, "mapping", "type", "t")] string mapping)
        {
            return _torrentManager.TryDownload(url, saveDirectory, mapping ?? "crap");
        }

        /// <summary>
        /// Download file.
        /// </summary>
        /// <param name="url">file URL</param>
        /// <param name="filename">name with which file will be saved.</param>
        /// <param name="saveDirectory">directory to save file.</param>
        /// <returns></returns>
        [RemoteCommand("Download file.", "download", "file", "d")]
        public bool DownloadFile([URLRequiredParameter("URL to download from.")] string url, 
            [Parameter("Name with which file will be saved.", typeof(string), false, "n", "name")] string filename,
            [DirectoryParameterAttribute("Directory to save file.", false)] string saveDirectory)
        {
            var webClient = new WebClient();
            webClient.DownloadFileAsync(new Uri(url),
                FilesHelper.GetDownloadFilePath(saveDirectory, filename, url));
            return true;
        }

        /// <summary>
        /// Start teamviewer.
        /// </summary>
        /// <remarks>No parameters.</remarks>
        [RemoteCommand("Start teamviewer.", "startteamviewer", "teamviewer", "tw")]
        public bool StartTeamviewer()
        {
            return _RunShell(ProgrammsFinder.GetTeamViewerPathFromRegistry());
        }

        /// <summary>
        /// Start teamviewer.
        /// </summary>
        [RemoteCommand("Close teamviewer.", "closeteamviewer", "teamviewerclose", "twc")]
        public bool CloseTeamviewer(
            [Parameter("Minutes before closing.", typeof(int), false, "m", "min")] int minutes = 0)
        {
            return _CloseIn("teamviewer", minutes);
        }

        /// <summary>
        /// Send folder as zip file.
        /// </summary>
        /// <param name="directory"> directory to send </param>
        /// <param name="mailAddress">Mail address to send.</param>
        /// <param name="size">Size to which archives must be splitted.</param>
        [RemoteCommand("Send folder as zip file.", "send")]
        public bool SendFolder([DirectoryParameterAttribute("Directory to send.", true)] string directory,
            [MailAddressParameter()] string mailAddress, 
            [Parameter("Size to which archives must be splitted.", typeof(string), false, "s", "minutes")] string size,
            [BoolParameterAttribute("folder would be deleted after zip", "folder will stay unchanged", "delete", "del")] bool delete,
            [PasswordParameter] string password)
        {
            int sizeBytes = _ConvertToBytes(size);

            var files = Zipper.ZipFolder(directory, sizeBytes, password).Select(x => new FileInfo(x));

            if(_mailer.MaxAttachSize < files.Sum(x => x.Length))
                _mailer.SendMail(mailAddress, "Zip archive", files.Select(x => x.FullName), string.Empty);
            else
            {
                var zippedFilesRootFolder = files.First().Directory.FullName;
                var result = UploaderManager.UploadFolder(zippedFilesRootFolder, null, sizeBytes, password, false);
                _SendUploadResult(result, mailAddress);
            }

            if (delete)
                Directory.Delete(directory);

            return true;
        }

        /// <summary>
        /// Upload file.
        /// </summary>
        /// <summary>
        /// <param name="zip">If flag exist - upload file zipped.</param>
        /// <param name="size">Size to which archives must be splitted</param>
        /// <param name="serviceType"></param>
        /// <param name="mail">mail addres to send</param>
        [RemoteCommand("Upload file to sharing service.", "fileupload")]
        public bool UploadFile(
            [RequiredParameter("Path to file to upload.", typeof(string), "f", "file", "filePath")] string filePath,
            [BoolParameter("Files will be zipped before uploaded",
                "File will be uploaded as is. Password and minutes parameters are ignored.", "z", "zip")] bool zip,
            [Parameter("Size to which archives must be splitted.", typeof(string), false, "s", "minutes")] string size,
            [Parameter("Service, which should be used for file sharing.",
                typeof(string), false, "service", "serviceType")] string serviceType,
            [MailAddressParameter()] string mail, [PasswordParameter()] string password)
        {
            int sizeBytes = _ConvertToBytes(size);
            var service = _GetServiceFromParameter(serviceType);

            var result = UploaderManager.UploadFile(filePath, service, sizeBytes, password, zip);
            _SendUploadResult(result, mail);

            return true;
        }

        /// <summary>
        /// Upload folder.
        /// </summary>
        /// <summary>
        /// <param name="zip">If flag exist - upload file zipped.</param>
        /// <param name="size">Size to which archives must be splitted</param>
        /// <param name="serviceType"></param>
        /// <param name="mail">mail addres to send</param>
        [RemoteCommand("Upload folder to sharing service.", "upload", "ul")]
        public bool UploadFolder([DirectoryParameter("Directory to upload.", true)] string directory,
            [BoolParameter(
                "Folder will be zipped before uploading.",
                "Folder will be uploaded as is without zipping.", "z", "zip")] bool zip,
            [Parameter("Size to which archives must be split.", typeof(string), false, "s", "minutes")] string size,
            [Parameter("Service, which should be used for file sharing.",
                typeof(string), false, "service", "serviceType")] string serviceType,
            [MailAddressParameter()] string mail, [PasswordParameter()]string password)
        {
            int sizeBytes = _ConvertToBytes(size);

            if (sizeBytes == 0)
                sizeBytes = 10 * 1024 * 1024;

            var service = _GetServiceFromParameter(serviceType);

            var result = UploaderManager.UploadFolder(directory, service, sizeBytes, password, zip);
            _SendUploadResult(result, mail);

            return true;
        }

        #endregion

        #region Private methods

        #region Parameters helpers

        private SharingServices? _GetServiceFromParameter(string serviceName)
        {
            // If we could parse service type from name - return this type, otherwise - return null.
            SharingServices service;
            if (serviceName != null &&  Enum.TryParse<SharingServices>(serviceName, true, out service))
                return service;
            else
                return null;
        }

        /// <summary>
        /// Convert string to minutes in bytes.
        /// </summary>
        /// <param name="sizeString">String with minutes. If string contains only numbers - it
        /// means it contains minutes in bytes, otherwise string must be XXX_M or XXX_G format 
        /// which means that digits are in MegaBytes or GigaBytes units.</param>
        /// <returns></returns>
        private int _ConvertToBytes(string sizeString)
        {
            const int KILO_MULTIPLIER = 1024;
            const int MEGABYTE_MULTIPLIER = KILO_MULTIPLIER * KILO_MULTIPLIER;
            const int GIGABYTE_MULTIPLIER = KILO_MULTIPLIER * MEGABYTE_MULTIPLIER;

            if (string.IsNullOrEmpty(sizeString))
                return 100 * MEGABYTE_MULTIPLIER;

            int size;
            int.TryParse(sizeString, out size);

            if (size == 0)
            {
                var sizeStrings = sizeString.Split(new char[] { '_' });
                if (sizeStrings == null)
                    throw new ArgumentException();
                var sizeMultiplier = string.Compare(sizeStrings[1], "M", true) == 0 ?
                    MEGABYTE_MULTIPLIER : GIGABYTE_MULTIPLIER;
                size = int.Parse(sizeStrings[0]) * sizeMultiplier;
            }

            return size;
        }
        #endregion

        private void _SendUploadResult(IEnumerable<string> result, string mail)
        {
            var filesURLs = string.Empty;

            foreach (var res in result)
                filesURLs += Environment.NewLine + res;

            // Send mail.
            _mailer.SendMail(mail, "Uploaded", filesURLs);
        }

        private bool _RunShell(string parameters)
        {
            var process = new Process();
            process.StartInfo.FileName = "cmd";
            process.StartInfo.Arguments = parameters;
            return process.Start();
        }

        private bool _CloseIn(string programName, int minutes)
        {
            if (minutes < 1)
                return _CloseAllProcess(programName);

            var timer = new System.Timers.Timer(minutes);
            timer.Elapsed += (_, __) => _CloseAllProcess(programName);

            return true;
        }

        private bool _CloseAllProcess(string name)
        {
            var  processes = Process.GetProcessesByName(name);
            foreach (Process proc in processes)
                proc.Kill();

            return processes.Any();
        }

        private ITorrentManager _torrentManager;
        private IMailer _mailer;

        #endregion
    }
}
